
/*
    Similar code to 'dijit._editor.selection' (Dojo v. 1.1.0) but adapted for application to
    iframes and modified to work 'better?' in Opera 9.x browser
*/

dojo.provide("msos.selection");

var sel_dump = null;
if (djConfig.msos_debug) {
	dojo.require("msos.dumper");
	sel_dump = msos.dumper.dump;
}


msos.selection.generate_object = function (win, doc, ifr) {
    var fb_sel = this;

    this.debug = false;

    // Returns the current selection object
    this.get_selection = function() {
	// Order is important here! (Opera)
	if	(window.getSelection)	{ return win.getSelection();	}
	else if (document.selection)	{ return win.document.selection;}
	else				{ return null;			}
    }

    // Returns a range for the current selection
    this.get_range = function(sel) {
	if (!sel) return null;
	// Order is important here! (Opera)
	if	(window.getSelection)	{ return sel.getRangeAt(0);	}
	else if (document.selection)	{ return sel.createRange();	}
	else				{ return null;			}
    }

    // Returns a new (control?) range object
    this.create_range = function() {
	// Order is important here! (Opera)
	if	(window.getSelection)	{ return doc.createRange();	        }
	else if (document.selection)	{ return doc.body.createControlRange();	}
	else				{ return null;			        }
    }

    this.set_focus = function() {
        // Setting focus is an absolute 'must' for Opera and Moz (maybe others)
        if (dojo.isOpera) {
            ifr.blur();     // Why this works, I don't know. Without the 'blur'
            ifr.focus();    // and then 'focus', Opera eventually loses its cursor position
        } else {
            win.focus();
          }
    }

    this.selection_str_end = function () {
        var cursor_pos = {
            start : 0,
            end : 0,
            offset : 0,
            offset_tar : 0,
            pos_debug : {},
            string : '',
            range : null,
            select : null,
            target : null,
            max : 0
        };
	var start = 0;
	var end = 0;
        var child = null;
        var target = null;

        // Get the current selection/range
	var win_sel = fb_sel.get_selection();
	var win_rng = fb_sel.get_range(win_sel);

        if (window.getSelection) {
            var offset = 0;
            var current = null;

            child = win_sel.anchorNode || null;
            target = child.parentNode || null;

            start = win_sel.anchorOffset;
            end =   win_sel.focusOffset;
            cursor_pos.string = win_sel.toString() || 'no text selected';

            if (target && target.hasChildNodes()) current = target.firstChild;

            while (current && (current !== child)) {
                if (current.nodeType == 3) {
                    offset += current.nodeValue.length;
                }
                current = current.nextSibling;
            }
            cursor_pos.offset = offset;

            offset = 0;

            if (target) { current = target.parentNode.firstChild;   }
            else        { current = null;                           }

            while (current && (current !== target)) {
                if (current.nodeType == 3) {
                    offset += current.nodeValue.length;
                }
                current = current.nextSibling;
            }
            cursor_pos.offset_tar = offset;

        } else if (document.selection) {
            var str_cnt = 0;
            var end_cnt = 0;
            // clone our original range
            var tmp_rng = win_rng.duplicate();
            target = win_rng.parentElement();
            if (tmp_rng) {
                while (tmp_rng.moveStart("character", -1) != 0) { start++; }
                while (  tmp_rng.moveEnd("character", -1) != 0) { end++;   }
            }
            // create an empty container range
            var cnt_rng = win.document.body.createTextRange();
            cnt_rng.moveToElementText(target);
            if (cnt_rng) {
                while (cnt_rng.moveStart("character", -1) != 0) { str_cnt++; }
            }
            start = start - str_cnt;
            end =   end   - str_cnt;

            cursor_pos.string = win_rng.text || 'no text selected';
          }
        cursor_pos.range = win_rng;
        cursor_pos.select = win_sel;
	cursor_pos.start = start;
	cursor_pos.end = end;
        cursor_pos.target = target;
        cursor_pos.max = msos.selection.get_node_txt_length(target);

	cursor_pos.pos_debug = {
	    str :           cursor_pos.start,
	    end :           cursor_pos.end,
            max :           cursor_pos.max,
            offset :        cursor_pos.offset,
            tar_off :       cursor_pos.offset_tar,
            string :        cursor_pos.string
        }
        if (child) {
            cursor_pos.pos_debug.child_name = child.nodeName;
            cursor_pos.pos_debug.child_id = (child.id || 'na');
        }
        if (target) {
            cursor_pos.pos_debug.target_name = target.nodeName;
            cursor_pos.pos_debug.target_id = (target.id || 'na');
        }
	cursor_pos.start = cursor_pos.start > 0 ? cursor_pos.start : 0;
	cursor_pos.end =   cursor_pos.end >   0 ? cursor_pos.end   : 0;
	cursor_pos.end =  (cursor_pos.end < cursor_pos.start) ? cursor_pos.start : cursor_pos.end;

        cursor_pos.pos_debug.str_rev = cursor_pos.start;
        cursor_pos.pos_debug.end_rev = cursor_pos.end;

	if (sel_dump && fb_sel.debug) {
	    sel_dump.setObjectName('Cursor Start/End Hash');
	    sel_dump.popup(cursor_pos.pos_debug);
	}
        return cursor_pos;
    }

    this.set_start_end = function(target, start, end) {
        var flag_start = true;
        var offset = 0;
        var range = null;

        if (window.getSelection) {
            var child = null;

            // Trick to reset sibiling '#text' nodes to one continuous node.
            // 'edit_insert_html' adds a '#text' node per character in Moz & Opera,
            target.innerHTML = target.innerHTML;

            range = fb_sel.create_range();
            
            if (target && target.hasChildNodes()) child = target.firstChild;

            while (child) {
                if (child.nodeType == 3) {
                    if (flag_start) {
                        // Find the child with start index position
                        if (start <= (child.nodeValue.length + offset)) {
                            range.setStart(child, start-offset);
                            flag_start = false;  
                        }
                    }
                    if (!flag_start) {
                        // Then the child with end index position
                        if (end <= (child.nodeValue.length + offset)) {
                            range.setEnd(child, end-offset);
                            break;  
                        }
                    }
                    offset += child.nodeValue.length;
                }
                child = child.nextSibling;
            }
        } else if (document.selection) {
            fb_sel.selectElementChildren(target);

            // Get the current selection/range
            var sel = fb_sel.get_selection();
            range = fb_sel.get_range(sel);

            range.moveStart('character', start);
            range.collapse('true');
            if (start != end) range.moveEnd('character', (end - start));
          }
        return range;
    }

    this.getType = function() {
	// summary: Get the selection type (like dojo.doc.select.type in IE).
        if (window.getSelection) {
	    var stype = "text";

	    // Check if the actual selection is a CONTROL (IMG, TABLE, HR, etc...).
	    var oSel, oRange;
	    try {
		oSel = fb_sel.get_selection();
                oRange = fb_sel.get_range(oSel);
	    } catch(e){ console.error('generate_object - getType -> failed: ' + e) }

	    if(oSel && oSel.rangeCount==1) {
		if((oRange.startContainer == oRange.endContainer) &&
		  ((oRange.endOffset - oRange.startOffset) == 1) &&
		   (oRange.startContainer.nodeType != 3 /* text node*/)){
			stype = "control";
		}
	    }
	    return stype;
	} else if (document.selection) {
            return doc.selection.type.toLowerCase();
	} else {
            return null;
          }
    }

    this.getSelectedText = function() {
	// summary:
	// Return the text (no html tags) included in the current selection or null if no text is selected
        if (window.getSelection) {
	    var selection = fb_sel.get_selection();
	    if (selection) {
		return selection.toString();
	    }
	} else if (document.selection) {
	    if (fb_sel.getType() == 'control') {
		return null;
	    }
	    var oSel = fb_sel.get_selection();
            var oRange = fb_sel.get_range(oSel); 
	    return oRange.text;
	  }
        return null;
    }

    this.getSelectedHtml = function() {
	// summary:
	// Return the html of the current selection or null if unavailable
        if (window.getSelection) {
	    var selection = fb_sel.get_selection();
            var range = fb_sel.get_range(selection);
	    if (selection && selection.rangeCount) {
		var frag = range.cloneContents();
		var div = doc.createElement("div");
		div.appendChild(frag);
		return div.innerHTML;
	    }
	    return null;
	} else if (document.selection) {
	    if (fb_sel.getType() == 'control'){
		return null;
	    }
	    var oSel = fb_sel.get_selection();
            var oRange = fb_sel.get_range(oSel);
	    return oRange.htmlText;
	} else {
            return null;
          }
    }

    this.getSelectedElement = function() {
	// summary:
	// Retrieves the selected element (if any), just in the case that
	// a single element (object like and image or a table) is
	// selected.
	if(fb_sel.getType() == "control"){
            if (window.getSelection) {
		var selection = fb_sel.get_selection();
		return selection.anchorNode.childNodes[ selection.anchorOffset ];
	    } else if (document.selection) {
                var oSel = fb_sel.get_selection();
                var oRange = fb_sel.get_range(oSel);
		if (oRange && oRange.item) {
		    return doc.selection.createRange().item(0);
		}
	      }
	}
	return null;
    }

    this.getParentElement = function() {
	// summary:
	// Get the parent element of the current selection
	if (fb_sel.getType() == "control") {
	    var p = fb_sel.getSelectedElement();
	    if (p) { return p.parentNode; }
	} else {
            if (window.getSelection) {
		var selection = fb_sel.get_selection();
		if (selection) {
		    var node = selection.anchorNode;
		    while(node && (node.nodeType != 1)){ // not an element
			node = node.parentNode;
		    }
		    return node;
		}
	    } else if (document.selection) {
                var oSel = fb_sel.get_selection();
                var oRange = fb_sel.get_range(oSel);
		return oRange.parentElement();
	      }
	  }
	return null;
    }

    this.hasAncestorElement = function(/*String*/tagName /* ... */) {
	// summary:
	// Check whether current selection has a  parent element which is
	// of type tagName (or one of the other specified tagName)
	return fb_sel.getAncestorElement.apply(arguments) != null;
    }

    this.getAncestorElement = function(/*String*/tagName /* ... */){
	// summary:
	// Return the parent element of the current selection which is of
	// type tagName (or one of the other specified tagName)

	var node = fb_sel.getSelectedElement() || fb_sel.getParentElement();
	return fb_sel.getParentOfType(node, arguments);
    }

    this.isTag = function(/*DomNode*/node, /*Array*/tags) {
	if (node && node.tagName) {
	    var _nlc = node.tagName.toLowerCase();
	    for(var i=0; i<tags.length; i++) {
		var _tlc = String(tags[i]).toLowerCase();
		if (_nlc == _tlc) {
		    return _tlc;
		}
	    }
	}
	return "";
    }

    this.getParentOfType = function(/*DomNode*/node, /*Array*/tags) {
	while(node) {
	    if (fb_sel.isTag(node, tags).length) {
		return node;
	    }
	    node = node.parentNode;
	}
	return null;
    }

    this.collapse = function(/*Boolean*/beginning) {
        // summary: clear current selection
	if (window.getSelection) {
	    var selection = fb_sel.get_selection();
	    if(selection.removeAllRanges) { // Mozilla
	        if (beginning) {
	            selection.collapseToStart();
	        } else {
	            selection.collapseToEnd();
	          }
	    } else { // Safari
	        // pulled from WebCore/ecma/kjswin.cpp, line 2536
	        selection.collapse(beginning);
	      }
	} else if (document.selection) { // IE
	    var oSel = fb_sel.get_selection();
            var oRange = fb_sel.get_range(oSel);
	    oRange.collapse(beginning);
	    oRange.select();
	  }
    }

    this.remove = function(){
	// summary: delete current selection
        var _s = null;
        if (window.getSelection) { // Opera && Moz
	    _s = fb_sel.get_selection();
            for ( var i = 0 ; i < _s.rangeCount ; i++ ) {
                    _s.getRangeAt(i).deleteContents() ;
            }
	    return _s;
	} else { //IE
            _s = fb_sel.get_selection();
	    if (_s.type.toLowerCase() != "none") {
		_s.clear();
            }
	    return _s;
          }
    }

    this.selectElementChildren = function(/*DomNode*/element) {
	// summary:
	// clear previous selection and select the content of the node
	// (excluding the node itself)
        if (window.getSelection) {
	    var selection = fb_sel.get_selection();
	    if (selection.setBaseAndExtent) { // Safari
		selection.setBaseAndExtent(element, 0, element, element.innerText.length - 1);
            } else if (selection.removeAllRanges) { // Opera && Mozilla
                var range = fb_sel.create_range(); 
                range.selectNodeContents(element);
		selection.removeAllRanges();
		selection.addRange(range);
              }
	} else if (document.selection && document.body.createTextRange) { // IE
	    var range = element.ownerDocument.body.createTextRange();
	    range.moveToElementText(element);
	    range.select();
          }
    }

    this.selectElement = function(/*DomNode*/element) {
	// summary:
	// clear previous selection and select element (including all its children)
	var range = null;
        if (win.getSelection) {
	    var selection = fb_sel.get_selection();
	    // FIXME: does this work on Safari?
	    if(selection.removeAllRanges){ // Opera & Mozilla
		range = fb_sel.create_range();
		range.selectNode(element);
		selection.removeAllRanges();
		selection.addRange(range);
	    }
	} else if (document.selection && document.body.createTextRange) { // IE
	    try {
		range = fb_sel.create_range();
		range.addElement(element);
		range.select();
	    } catch(e) {
		fb_sel.selectElementChildren(element);
	      }
          }
    }
}


// --------------------------
// Helper functions
// --------------------------

msos.selection.disableSelection = function (element) {
    if (dojo.isMozilla) {
	element.style.MozUserSelect = "none";
    } else {
	if (dojo.isSafari) {
	    element.style.KhtmlUserSelect = "none";
	} else {
	    if (dojo.isIE)      { element.unselectable = "on"; }
            else                { return false; }
	  }
    }
    return true;
}

// Get the text length of a node
msos.selection.get_node_txt_length = function(element) {
        var elm_length = 0;
        var tmp_node = null;

        if (element && element.hasChildNodes()) tmp_node = element.firstChild;

        while (tmp_node) {
                if (tmp_node.nodeType == 3) elm_length += tmp_node.nodeValue.length;
                tmp_node = tmp_node.nextSibling;
        }
        return elm_length;
}